/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.wasm.test

import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.wasm.WasmPlatforms
import org.jetbrains.kotlin.platform.wasm.WasmTarget
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.FirParser
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.backend.ir.IrBackendInput
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.builders.configureFirHandlersStep
import org.jetbrains.kotlin.test.builders.firHandlersStep
import org.jetbrains.kotlin.test.configuration.commonFirHandlersForCodegenTest
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.IGNORE_BACKEND_K2_MULTI_MODULE
import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives.LANGUAGE
import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.model.ValueDirective
import org.jetbrains.kotlin.test.frontend.fir.Fir2IrResultsConverter
import org.jetbrains.kotlin.test.frontend.fir.FirFrontendFacade
import org.jetbrains.kotlin.test.frontend.fir.FirMetaInfoDiffSuppressor
import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact
import org.jetbrains.kotlin.test.frontend.fir.handlers.FirCfgConsistencyHandler
import org.jetbrains.kotlin.test.frontend.fir.handlers.FirCfgDumpHandler
import org.jetbrains.kotlin.test.frontend.fir.handlers.FirDumpHandler
import org.jetbrains.kotlin.test.frontend.fir.handlers.FirResolvedTypesVerifier
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.AdditionalSourceProvider
import org.jetbrains.kotlin.test.services.SplittingModuleTransformerForBoxTests
import org.jetbrains.kotlin.test.services.SplittingTestConfigurator
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibSerializerFacade
import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade
import org.jetbrains.kotlin.wasm.test.handlers.WasiBoxRunner
import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner
import org.jetbrains.kotlin.wasm.test.handlers.WasmDebugRunner
import org.jetbrains.kotlin.wasm.test.providers.WasmJsSteppingTestAdditionalSourceProvider

fun TestConfigurationBuilder.configureCodegenFirHandlerSteps() {
    configureFirHandlersStep {
        commonFirHandlersForCodegenTest()
    }
    useAfterAnalysisCheckers(
        ::FirMetaInfoDiffSuppressor
    )
}

abstract class AbstractFirWasmTest(
    targetBackend: TargetBackend,
    targetPlatform: TargetPlatform,
    pathToTestDir: String,
    testGroupOutputDirPrefix: String,
) : AbstractWasmBlackBoxCodegenTestBase<FirOutputArtifact, IrBackendInput, BinaryArtifacts.KLib>(
    FrontendKinds.FIR, targetBackend, targetPlatform, pathToTestDir, testGroupOutputDirPrefix
) {
    override val frontendFacade: Constructor<FrontendFacade<FirOutputArtifact>>
        get() = ::FirFrontendFacade

    override val frontendToBackendConverter: Constructor<Frontend2BackendConverter<FirOutputArtifact, IrBackendInput>>
        get() = ::Fir2IrResultsConverter

    override val backendFacade: Constructor<BackendFacade<IrBackendInput, BinaryArtifacts.KLib>>
        get() = ::FirWasmKlibSerializerFacade

    override val afterBackendFacade: Constructor<AbstractTestFacade<BinaryArtifacts.KLib, BinaryArtifacts.Wasm>>
        get() = ::WasmBackendFacade

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            defaultDirectives {
                +LanguageSettingsDirectives.ALLOW_KOTLIN_PACKAGE
                DiagnosticsDirectives.DIAGNOSTICS with listOf("-infos")
                FirDiagnosticsDirectives.FIR_PARSER with FirParser.Psi
            }

            firHandlersStep {
                useHandlers(
                    ::FirDumpHandler,
                    ::FirCfgDumpHandler,
                    ::FirCfgConsistencyHandler,
                    ::FirResolvedTypesVerifier,
                )
            }
            defaultDirectives {
                LANGUAGE with listOf(
                    "-${LanguageFeature.IrIntraModuleInlinerBeforeKlibSerialization.name}",
                    "-${LanguageFeature.IrCrossModuleInlinerBeforeKlibSerialization.name}"
                )
            }
        }
    }
}

open class AbstractFirWasmJsTest(
    pathToTestDir: String,
    testGroupOutputDirPrefix: String,
) : AbstractFirWasmTest(TargetBackend.WASM_JS, WasmPlatforms.wasmJs, pathToTestDir, testGroupOutputDirPrefix) {
    override val wasmBoxTestRunner: Constructor<AnalysisHandler<BinaryArtifacts.Wasm>>
        get() = ::WasmBoxRunner

    override val wasmTarget: WasmTarget
        get() = WasmTarget.JS
}

open class AbstractFirWasmJsCodegenBoxTest(
    pathToTestDir: String = "compiler/testData/codegen/",
    testGroupOutputDirPrefix: String = "codegen/firBox/"
) : AbstractFirWasmJsTest(
    pathToTestDir = pathToTestDir,
    testGroupOutputDirPrefix = testGroupOutputDirPrefix
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.configureCodegenFirHandlerSteps()
    }
}

open class AbstractFirWasmJsCodegenBoxWithInlinedFunInKlibTest(
    pathToTestDir: String = "compiler/testData/codegen/",
    testGroupOutputDirPrefix: String = "codegen/boxInlKlib/"
) : AbstractFirWasmJsCodegenBoxTest(
    pathToTestDir = pathToTestDir,
    testGroupOutputDirPrefix = testGroupOutputDirPrefix
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            defaultDirectives {
                LANGUAGE with listOf(
                    "+${LanguageFeature.IrIntraModuleInlinerBeforeKlibSerialization.name}",
                    "+${LanguageFeature.IrCrossModuleInlinerBeforeKlibSerialization.name}"
                )
            }
        }
    }
}

open class AbstractFirWasmJsSyntheticAccessorsTest(
    pathToTestDir: String = "compiler/testData/klib/syntheticAccessors",
    testGroupOutputDirPrefix: String = "codegen/syntheticAccessors/"
) : AbstractFirWasmJsCodegenBoxWithInlinedFunInKlibTest(
    pathToTestDir = pathToTestDir,
    testGroupOutputDirPrefix = testGroupOutputDirPrefix
)

open class AbstractFirWasmJsCodegenSplittingWithInlinedFunInKlibTest() : AbstractFirWasmJsCodegenBoxWithInlinedFunInKlibTest(
    testGroupOutputDirPrefix = "codegen/boxSplitted/"
) {
    override val additionalIgnoreDirectives: List<ValueDirective<TargetBackend>>?
        get() = listOf(IGNORE_BACKEND_K2_MULTI_MODULE)

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        @OptIn(TestInfrastructureInternals::class)
        builder.useModuleStructureTransformers(
            ::SplittingModuleTransformerForBoxTests
        )
        builder.useMetaTestConfigurators(::SplittingTestConfigurator)
    }
}

open class AbstractFirWasmJsCodegenBoxInlineTest : AbstractFirWasmJsTest(
    "compiler/testData/codegen/boxInline/",
    "codegen/firBoxInline/"
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.configureCodegenFirHandlerSteps()
    }
}

open class AbstractFirWasmJsCodegenInteropTest : AbstractFirWasmJsTest(
    "compiler/testData/codegen/boxWasmJsInterop",
    "codegen/firWasmJsInterop"
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.configureCodegenFirHandlerSteps()
    }
}

open class AbstractFirWasmJsTranslatorTest : AbstractFirWasmJsTest(
    "js/js.translator/testData/box/",
    "js.translator/firBox"
)

open class AbstractFirWasmJsSteppingTest(
    testGroupOutputDirPrefix: String = "debug/stepping/firBox"
) : AbstractFirWasmJsTest(
    "compiler/testData/debug/stepping/",
    testGroupOutputDirPrefix
) {
    override val wasmBoxTestRunner: Constructor<AnalysisHandler<BinaryArtifacts.Wasm>>
        get() = ::WasmDebugRunner

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            useAdditionalSourceProviders(::WasmJsSteppingTestAdditionalSourceProvider)
            defaultDirectives {
                +WasmEnvironmentConfigurationDirectives.GENERATE_SOURCE_MAP
                +WasmEnvironmentConfigurationDirectives.FORCE_DEBUG_FRIENDLY_COMPILATION
                +WasmEnvironmentConfigurationDirectives.SOURCE_MAP_INCLUDE_MAPPINGS_FROM_UNAVAILABLE_FILES
            }
        }
    }
}

open class AbstractFirWasmJsSteppingWithInlinedFunInKlibTest(
    testGroupOutputDirPrefix: String = "debug/firSteppingWithInlinedFunInKlib/"
) : AbstractFirWasmJsSteppingTest(
    testGroupOutputDirPrefix = testGroupOutputDirPrefix
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            defaultDirectives {
                LANGUAGE with listOf(
                    "+${LanguageFeature.IrIntraModuleInlinerBeforeKlibSerialization.name}",
                    "+${LanguageFeature.IrCrossModuleInlinerBeforeKlibSerialization.name}"
                )
            }
        }
    }
}

open class AbstractFirWasmJsSteppingSplitTest : AbstractFirWasmJsSteppingTest(
    testGroupOutputDirPrefix = "debug/firSteppingSplit/"
) {
    override val additionalIgnoreDirectives: List<ValueDirective<TargetBackend>>?
        get() = listOf(IGNORE_BACKEND_K2_MULTI_MODULE)

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            @OptIn(TestInfrastructureInternals::class)
            useModuleStructureTransformers(
                ::SplittingModuleTransformerForBoxTests
            )
            useMetaTestConfigurators(::SplittingTestConfigurator)
        }
    }
}

open class AbstractFirWasmJsSteppingSplitWithInlinedFunInKlibTest : AbstractFirWasmJsSteppingWithInlinedFunInKlibTest(
    testGroupOutputDirPrefix = "debug/firSteppingSplit/"
) {
    override val additionalIgnoreDirectives: List<ValueDirective<TargetBackend>>?
        get() = listOf(IGNORE_BACKEND_K2_MULTI_MODULE)

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            @OptIn(TestInfrastructureInternals::class)
            useModuleStructureTransformers(
                ::SplittingModuleTransformerForBoxTests
            )
            useMetaTestConfigurators(::SplittingTestConfigurator)
        }
    }
}

open class AbstractFirWasmWasiTest(
    pathToTestDir: String,
    testGroupOutputDirPrefix: String,
) : AbstractFirWasmTest(TargetBackend.WASM_WASI, WasmPlatforms.wasmWasi, pathToTestDir, testGroupOutputDirPrefix) {
    override val wasmBoxTestRunner: Constructor<AnalysisHandler<BinaryArtifacts.Wasm>>
        get() = ::WasiBoxRunner

    override val wasmTarget: WasmTarget
        get() = WasmTarget.WASI

    override val additionalSourceProvider: Constructor<AdditionalSourceProvider>?
        get() = ::WasmWasiBoxTestHelperSourceProvider

    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.defaultDirectives {
            +WasmEnvironmentConfigurationDirectives.GENERATE_DWARF
        }
    }
}

open class AbstractFirWasmWasiCodegenBoxTest(
    testGroupOutputDirPrefix: String = "codegen/wasi/"
) : AbstractFirWasmWasiTest(
    pathToTestDir = "compiler/testData/codegen/",
    testGroupOutputDirPrefix = testGroupOutputDirPrefix
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.configureCodegenFirHandlerSteps()
    }
}

open class AbstractFirWasmWasiCodegenBoxWithInlinedFunInKlibTest : AbstractFirWasmWasiCodegenBoxTest(
    testGroupOutputDirPrefix = "codegen/wasiBoxInlKlib/"
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        with(builder) {
            defaultDirectives {
                LANGUAGE with listOf(
                    "+${LanguageFeature.IrIntraModuleInlinerBeforeKlibSerialization.name}",
                    "+${LanguageFeature.IrCrossModuleInlinerBeforeKlibSerialization.name}"
                )
            }
        }
    }
}

open class AbstractFirWasmTypeScriptExportTest : AbstractFirWasmJsTest(
    "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/typescript-export/wasm/",
    "typescript-export/"
) {
    override fun configure(builder: TestConfigurationBuilder) {
        super.configure(builder)
        builder.defaultDirectives {
            +WasmEnvironmentConfigurationDirectives.CHECK_TYPESCRIPT_DECLARATIONS
        }
    }
}
